﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Input;
#if !WINRT
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Threading;
#else

using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;

#endif

namespace MishraReader
{
    //TODO: Test Validation Rule
    //TODO: Add events
    //TODO: Try a ContentPresenter in the WindowTemplate            
    //TODO: ContentTemplate is DataTemplate : Which defines how content in a ContentControl in an is displayed: http://msdn.microsoft.com/en-us/library/system.windows.controls.contentcontrol.contenttemplate.aspx
    //TODO: ControlTemplate/WindowTemplate : the ControlTemplate, which defines the default appearance and behavior of a control
    /// <summary>
    ///   A dialog that acts like a normal ContentControl.
    /// </summary>
    public class Dialog : ContentControl, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        ///   AllowsTransparency Dependency Property
        /// </summary>
        public static readonly DependencyProperty AllowsTransparencyProperty =
            DependencyProperty.Register("AllowsTransparency", typeof(bool), typeof(Dialog),
                                        new FrameworkPropertyMetadata(true));

        public static readonly DependencyProperty CancelCommandProperty =
            DependencyProperty.Register("CancelCommand", typeof(ICommand), typeof(Dialog), new FrameworkPropertyMetadata(null));

        public static readonly DependencyProperty ShowingProperty =
            DependencyProperty.Register("Showing", typeof(bool), typeof(Dialog),
                                        new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                                                                      ShowingPropertyChangedCallback));

        public static readonly DependencyProperty TitleProperty =
            DependencyProperty.Register("Title", typeof(string), typeof(Dialog), new FrameworkPropertyMetadata(null));

        public static readonly DependencyProperty WindowTemplateProperty =
            DependencyProperty.Register("WindowTemplate", typeof(ControlTemplate), typeof(Dialog), new FrameworkPropertyMetadata(null));

        //An event handler to catch the Window closing event and Hide() the Window instead.
        private readonly EventHandler _dialogWindowClosedEventHandler;
        private readonly TunnelDataTemplateSelector _tunnelDataTemplateSelector;
        //Whether we've already dispatched a message to ourselves to update the state of the dialog
        //at the next chance in the message pump. Delay doing this expensive work until all the properties
        //are settled.
        private bool _hasDispatched;
        private DataTemplateSelector _oldSurrogateContentTemplateSelector;
        private Window _parentWindow;
        private DataTemplateSelector _surrogateContentTemplateSelector;
        private Window _window;

        static Dialog()
        {
            //Standard default style.
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Dialog), new FrameworkPropertyMetadata(typeof(Dialog)));
        }

        public Dialog()
        {
            //The a ContentPresenter which finds the DataTemplate for our logical tree and gives it to the TunnelDataTemplateSelector to give to the Window.
            var templateFinder = new TemplateFinder();
            AddLogicalChild(templateFinder);
            _tunnelDataTemplateSelector = new TunnelDataTemplateSelector(templateFinder);
            _dialogWindowClosedEventHandler = WindowClosedHandler;
        }

        /// <summary>
        ///   Gets or sets the AllowsTransparency property. This dependency property indicates ....
        /// </summary>
        public bool AllowsTransparency
        {
            get { return (bool)GetValue(AllowsTransparencyProperty); }
            set { SetValue(AllowsTransparencyProperty, value); }
        }

        /// <summary>
        ///   This command, if present, is invoked when the user cancels the dialog.
        /// </summary>
        public ICommand CancelCommand
        {
            get { return (ICommand)GetValue(CancelCommandProperty); }
            set { SetValue(CancelCommandProperty, value); }
        }

        /// <summary>
        ///   Whether the dialog is showing or not. Set this to true to show the dialog, false to hide it.
        /// </summary>
        public bool Showing
        {
            get { return (bool)GetValue(ShowingProperty); }
            set { SetValue(ShowingProperty, value); }
        }

        public DataTemplateSelector SurrogateContentTemplateSelector
        {
            get
            {
                if (_surrogateContentTemplateSelector == null)
                {
                    if (ContentTemplate == null)
                    {
                        return _tunnelDataTemplateSelector;
                    }
                    return null;
                }
                
                return _surrogateContentTemplateSelector;
            }
            set
            {
                if (_surrogateContentTemplateSelector != value)
                {
                    _surrogateContentTemplateSelector = value;
                    NotifySurrogateContentTemplateSelectorIfDifferent();
                }
            }
        }

        /// <summary>
        ///   This command, if present, is invoked when the user cancels the dialog.
        /// </summary>
        public String Title
        {
            get { return (String)GetValue(TitleProperty); }
            set { SetValue(TitleProperty, value); }
        }

        /// <summary>
        ///   This command, if present, is invoked when the user cancels the dialog.
        /// </summary>
        public ControlTemplate WindowTemplate
        {
            get { return (ControlTemplate)GetValue(WindowTemplateProperty); }
            set { SetValue(WindowTemplateProperty, value); }
        }

        protected void Notify(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        ///   Provides derived classes an opportunity to handle changes to the AllowsTransparency property.
        /// </summary>
        protected virtual void OnAllowsTransparencyChanged(bool oldAllowsTransparency, bool newAllowsTransparency)
        {
        }

        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);
            if (e.Property == ContentTemplateSelectorProperty)
            {
                SurrogateContentTemplateSelector = (DataTemplateSelector)e.NewValue;
            }
            else if (e.Property == ContentTemplateProperty)
            {
                NotifySurrogateContentTemplateSelectorIfDifferent();
            }
        }

        /// <summary>
        ///   When the parent changes, tries to find the parent Window.
        /// </summary>
        /// <param name="oldParent"> The previous parent. </param>
        protected override void OnVisualParentChanged(DependencyObject oldParent)
        {
            base.OnVisualParentChanged(oldParent);
            _parentWindow = LogicalTreeWalker.FindParentOfType<Window>(this);
        }

        private Binding BindControlToWindow(string propertyName, DependencyProperty dependencyProperty)
        {
            var binding = new Binding(propertyName)
                              {
                                  Source = this, 
                                  Mode = BindingMode.OneWay
                              };
            BindingOperations.SetBinding(_window, dependencyProperty, binding);
            return binding;
        }

        private void CloseAndDestroyDialog()
        {
            if (_window != null)
            {
                _window.Closed -= _dialogWindowClosedEventHandler;
                _window.Close();
                _window.ContentTemplateSelector = null;
                BindingOperations.ClearBinding(_window, ContentTemplateSelectorProperty);
                BindingOperations.ClearBinding(_window, ContentStringFormatProperty);
                BindingOperations.ClearBinding(_window, ContentTemplateProperty);
                BindingOperations.ClearBinding(_window, DataContextProperty);
                BindingOperations.ClearBinding(_window, ContentProperty);
                BindingOperations.ClearBinding(_window, Window.TitleProperty);
            }
        }

        private void CreateAndShowDialog()
        {
            if (WindowTemplate != null)
            {
                //Quietly fail if the cast is no good.                
                _window = WindowTemplate.LoadContent() as Window;
                if (_window != null)
                {
                    _window.Owner = _parentWindow;
                    _window.Closed += _dialogWindowClosedEventHandler;
                    var contentTemplateSelectorBinding = new Binding("SurrogateContentTemplateSelector")
                                                             {
                                                                 Source = this
                                                             };
                    BindingOperations.SetBinding(_window, ContentTemplateSelectorProperty, contentTemplateSelectorBinding);
                    //this._window.ContentTemplateSelector = this._tunnelDataTemplateSelector;                    
                    BindControlToWindow("ContentStringFormat", ContentStringFormatProperty);
                    BindControlToWindow("ContentTemplate", ContentTemplateProperty);
                    BindControlToWindow("DataContext", DataContextProperty);
                    BindControlToWindow("Content", ContentProperty);
                    BindControlToWindow("HorizontalContentAlignment", HorizontalContentAlignmentProperty);
                    BindControlToWindow("VerticalContentAlignment", VerticalContentAlignmentProperty);
                    BindControlToWindow("AllowsTransparency", Window.AllowsTransparencyProperty);
                    var originalTitle = _window.Title;
                    BindControlToWindow("Title", Window.TitleProperty);
                    if (Title == null)
                    {
                        _window.Title = originalTitle;
                    }

                    _window.ShowDialog();
                }
            }
        }

        private void DialogStateChanged(bool oldShowing)
        {
            if (!_hasDispatched)
            {
                Application.Current.Dispatcher.BeginInvoke(
                    DispatcherPriority.Normal,
                    (ThreadStart)(() => SynchronizeDialogShowing(oldShowing)));
                _hasDispatched = true;
            }
        }

        private void NotifySurrogateContentTemplateSelectorIfDifferent()
        {
            if (SurrogateContentTemplateSelector != _oldSurrogateContentTemplateSelector)
            {
                _oldSurrogateContentTemplateSelector = SurrogateContentTemplateSelector;
                Notify("SurrogateContentTemplateSelector");
            }
        }

        private void SynchronizeDialogShowing(bool oldShowing)
        {
            var newShowing = Showing;
            //If the state has changed, hide the Window.
            if (oldShowing && newShowing == false)
            {
                CloseAndDestroyDialog();
            }
            else if (oldShowing == false && newShowing)
            {
                Application.Current.Dispatcher.BeginInvoke(
                    DispatcherPriority.Normal,
                    (ThreadStart)(CreateAndShowDialog));
            }
            _hasDispatched = false;
        }

        private void WindowClosedHandler(object sender, EventArgs e)
        {
            ClearValue(ShowingProperty);
            //The Window was cancelled probably by the user pressing the X close. Execute the CancelCommand if one is provided to inform the ViewModel that this happened.
            if (CancelCommand != null && CancelCommand.CanExecute(null))
            {
                CancelCommand.Execute(null);
            }
        }

        private static void ShowingPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //We're in the middle of handling shifting Dependency Property
            //values. We don't know what value Showing is going to end up
            //with, so we don't want to do anything yet. So we just leave
            //a message for ourselves in the next message pump go around to
            //check the showing value.
            var dialog = (Dialog)d;
            //Only leave the message once.
            dialog.DialogStateChanged((bool)e.OldValue);
        }
    }
}